package com.bango.flex{
	import flash.desktop.NativeApplication;
	import flash.events.Event;
	import flash.events.GeolocationEvent;
	import flash.net.NetworkInfo;
	import flash.net.NetworkInterface;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestDefaults;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import flash.sensors.Geolocation;
	import flash.utils.Dictionary;
	
	import mx.formatters.DateFormatter;

	public class BangoLogger{
		
		private var _logEnabled:Boolean = BangoConstants.SETTING_LOGEVENTS;
		private var _useOfflineStorage:Boolean = BangoConstants.SETTING_USEOFFLINESTORAGE;
		private var _persistDatabaseConnection:Boolean = BangoConstants.SETTING_PERSIST_DATABASE_CONNECTION;
		private var _postURL:String = BangoConstants.SETTING_POST_URL;
		private var _findLocation:Boolean = BangoConstants.SETTING_LOCATION_FIND;
		
		private var _locationManager:Geolocation;
		
		private var _userAgent:String="";
		private var _applicationId:String="";
		private var _applicationType:String = "";
		private var _applicationChannel:String= "";
		private var _applicationVersion:String= "";
		private var _locationSource:String= "GPS";
		private var _userId:String="";
		
		private var _deviceBrand:String= "";
		private var _deviceMake:String= "";
		private var _deviceModel:String= "";
		private var _product:String= "";
		private var _operatingSystem:String= "";
		private var _operatingSystemVersion:String= "";
		private var _deviceVersion:String= "";
		
		private var _maximumEventsPerProfile:Number = 50;
		
		private var _profile:BangoProfile;
		private var _interface:BangoInterface;
		
		private var _initialized:Boolean = false;

		public function get deviceVersion():String
		{
			return _profile.DeviceVersion;
		}

		public function set deviceVersion(value:String):void
		{
			_deviceVersion = value;
			if(_profile != null)
			{
				_profile.DeviceVersion = value;
			}
		}

		public function get operatingSystemVersion():String
		{
			return _profile.OperatingSystemVersion;
		}

		public function set operatingSystemVersion(value:String):void
		{
			_operatingSystemVersion = value;
			if(_profile != null)
			{
				_profile.OperatingSystemVersion = value;
			}
		}

		public function get operatingSystem():String
		{
			return _profile.OperatingSystem;
		}

		public function set operatingSystem(value:String):void
		{
			_operatingSystem = value;
			if(_profile != null)
			{
				_profile.OperatingSystem = value;
			}
		}

		public function get product():String
		{
			return _profile.Product;
		}

		public function set product(value:String):void
		{
			_product = value;
			if(_profile != null)
			{
				_profile.Product = value;
			}
		}

		public function get deviceModel():String
		{
			return _profile.DeviceModel;
		}

		public function set deviceModel(value:String):void
		{
			_deviceModel = value;
			if(_profile != null)
			{
				_profile.DeviceModel = value;
			}
		}

		public function get deviceMake():String
		{
			return _profile.DeviceMake;
		}

		public function set deviceMake(value:String):void
		{
			_deviceMake = value;
			if(_profile != null)
			{
				_profile.DeviceMake = value;
			}
		}

		public function get deviceBrand():String
		{
			return _profile.DeviceBrand;
		}

		public function set deviceBrand(value:String):void
		{
			_deviceBrand = value;
			if(_profile != null)
			{
				_profile.DeviceBrand = value;
			}
		}

		public function get isInitialized():Boolean
		{
			return _initialized;
		}
		
		public function set isInitialized(value:Boolean):void
		{
			_initialized = value;
		}
		
		public function get logEnabled():Boolean
		{
			return _logEnabled;
		}

		public function set logEnabled(value:Boolean):void
		{
			_logEnabled = value;
		}

		public function get useOfflineStorage():Boolean
		{
			return _useOfflineStorage;
		}

		public function set useOfflineStorage(value:Boolean):void
		{
			_useOfflineStorage = value;
		}

		public function get persistDatabaseConnection():Boolean
		{
			return _persistDatabaseConnection;
		}

		public function set persistDatabaseConnection(value:Boolean):void
		{
			_persistDatabaseConnection = value;
		}

		public function get postURL():String
		{
			return _postURL;
		}

		public function set postURL(value:String):void
		{
			_postURL = value;
		}

		public function get findLocation():Boolean
		{
			return _findLocation;
		}

		public function set findLocation(value:Boolean):void
		{
			_findLocation = value;
		}

		public function get userAgent():String
		{
			return _userAgent;
		}

		public function set userAgent(value:String):void
		{
			_userAgent = value;
		}

		public function get applicationId():String
		{
			return _applicationId;
		}

		public function set applicationId(value:String):void
		{
			_applicationId = value;
		}

		public function get applicationType():String
		{
			return _applicationType;
		}

		public function set applicationType(value:String):void
		{
			_applicationType = value;
		}

		public function get applicationChannel():String
		{
			return _applicationChannel;
		}

		public function set applicationChannel(value:String):void
		{
			_applicationChannel = value;
		}

		public function get applicationVersion():String
		{
			return _applicationVersion;
		}

		public function set applicationVersion(value:String):void
		{
			_applicationVersion = value;
		}

		public function get locationSource():String
		{
			return _locationSource;
		}

		public function set locationSource(value:String):void
		{
			_locationSource = value;
		}

		public function get userId():String
		{
			return _userId;
		}

		public function set userId(value:String):void
		{
			_userId = value;
		}
		
		public function get maximumEventsPerProfile():Number
		{
			return _maximumEventsPerProfile;
		}
		
		public function set maximumEventsPerProfile(value:Number):void
		{
			_maximumEventsPerProfile = value;
		}

		
		/**
		 * Initialises the logging system. This should be called when starting the
		 * application, and is normally done with the startSession call.
		 * 
		 * @param applicationId
		 *            The application Id of the application. This should be setup in
		 *            the Bango Management Tools prior to use.
		 */
		public function initLogger(applicationId:String):void{
			
			_interface = new BangoInterface();
			_applicationVersion = getVersion();
			_applicationId= applicationId;
			
			if(_deviceBrand == "")
			{
				_deviceBrand= _interface.getProperty(BangoFieldNames.POST_DEVICEBRAND);
			}
			
			if(_deviceMake == "")
			{
				_deviceMake= _interface.getProperty(BangoFieldNames.POST_DEVICEMAKE);
			}
			
			if(_deviceModel == "")
			{
				_deviceModel= _interface.getProperty(BangoFieldNames.POST_DEVICEMODEL);
			}
			
			if(_product == "")
			{
				_product= _interface.getProperty(BangoFieldNames.POST_PRODUCT);
			}
				
			if(_operatingSystem == "")
			{
				_operatingSystem= _interface.getProperty(BangoFieldNames.POST_OPERATINGSYSTEM);
			}
					
			if(_operatingSystemVersion == "")
			{
				_operatingSystemVersion= _interface.getProperty(BangoFieldNames.POST_OPERATINGSYSTEMVERSION);
			}
						
			if(_deviceVersion == "")
			{
				_deviceVersion = _interface.getProperty(BangoFieldNames.POST_DEVICEVERSION);
			}
			
			getLocation();
			
			if(userId == null || userId == ""){
				userId = _interface.getProperty(BangoFieldNames.POST_USERID);
				if(userId == null || userId == "")
					userId = generateUserId();
			}
			
			if(_userAgent == null || _userAgent == ""){
				_userAgent = _interface.getProperty("UserAgent");
				if(_userAgent == null || _userAgent == "")
					_userAgent = URLRequestDefaults.userAgent;
			}
			
			
			_profile = new BangoProfile(BangoConstants.SDK_TYPE,
				BangoConstants.SDK_VERSION,
				applicationId,
				_applicationType,
				_applicationChannel,
				_applicationVersion,
				userId,
				BangoStatic.ConnectipnType(_interface.getProperty(BangoFieldNames.POST_CONNECTIONTYPE)),
				_interface.getProperty(BangoFieldNames.POST_ISROAMING),
				_locationSource,
				0,0,0,0,//alt long lat acc
				_interface.getProperty(BangoFieldNames.POST_OPERATOR),
				_interface.getProperty(BangoFieldNames.POST_OPERATORNAME),
				_interface.getProperty(BangoFieldNames.POST_COUNTRYISO),
				_deviceBrand,
				_deviceMake,
				_deviceModel,
				_product,
				_operatingSystem,
				_operatingSystemVersion,
				_deviceVersion,
				BangoStatic.OperatorConnectipnType(_interface.getProperty(BangoFieldNames.POST_OPERATORCONNECTIONTYPE)),
				_interface.getProperty(BangoFieldNames.POST_SIM));
			
			if(_useOfflineStorage){
				BangoDatabaseStorageHelper.InitDB();
				BangoDatabaseStorageHelper.setPersistDatabaseConnection(_persistDatabaseConnection);
				BangoDatabaseStorageHelper.LogProfile(_profile);
			}
			
			_initialized = true;
			
		}
		

		private function generateUserId():String{
			var result:String = "";
			try{
				var interfaces:Vector.<NetworkInterface> = NetworkInfo.networkInfo.findInterfaces();
				for(var i:uint = 0; i < interfaces.length; i++) {
					result = result + interfaces[i].hardwareAddress.toLowerCase();
				}
			}catch(err:Error){
				result = result + (new Date()).time.toString();
			}
			return MD5.encrypt(result);
		}
		
		/**
		 * Posts any stored events, with an option to post the current profile
		 * 
		 * @param notCurrentProfile
		 *            Whether or not the current profile should be posted.
		 * @param closeDatabase
		 *            Whether or not to close the database, this parameter is
		 *            ignored if offline storage is not being used
		 * @return Whether or not at least 1 profile and associated events were
		 *         posted successfully
		 */
		public function postEvents(notCurrentProfile:Boolean,closeDatabase:Boolean):Boolean{
			
			var returnValue:Boolean=false;
			var bangoProfiles:Array;
			
			if(_useOfflineStorage){
				
				bangoProfiles = BangoDatabaseStorageHelper.GetProfilesForPost(notCurrentProfile);
				
				for(var obj:Object in bangoProfiles){
					
					var profile:BangoProfile = bangoProfiles[obj] as BangoProfile;
					profile.Events = BangoDatabaseStorageHelper.GetEventsForPost(profile.getProfileId());
					
					if(profile.Events.length > 0){
						if(sendEvents(profile)){
							returnValue = true;
						}
					}else{
						if(profile.getProfileId() != BangoDatabaseStorageHelper.ProfileId()){
							BangoDatabaseStorageHelper.UpdateEventsPosted(profile.getProfileId());
							BangoDatabaseStorageHelper.UpdateProfileLock(profile.getProfileId(),false);
							BangoAgent.debug("Delete empty " + profile.getProfileId());
							BangoDatabaseStorageHelper.DeleteEventsPosted(profile.getProfileId());
						}
					}
				}
				
				if(closeDatabase){
					BangoDatabaseStorageHelper.CloseDatabase();
				}
				
			}else{
			
				returnValue = sendEvents(_profile);
				
			}
			
			return returnValue;
		}
		
		public function currentProfileHasEvent(eventName:String):Boolean{
			
			var returnValue:Boolean=false;
			var  events:Array = new Array();
			
			if(_useOfflineStorage){
				events = BangoDatabaseStorageHelper.GetEventsForPost(_profile.getProfileId());
			}else{
				events = _profile.Events;
			}
			
			for(var ev:String in events){
				if(events[ev].EventName == eventName)
					return true
			}
			
			return false;
			
		}
		
		/**
		 * Posts the supplied profile and associated events to the specified url.
		 * 
		 * @param profile
		 *            The profile to post to the specified url.
		 * @return Whether or not the profile and associated events were posted
		 *         successfully.
		 */
		public function sendEvents(profile:BangoProfile):Boolean{
			var returnValue:Boolean = true;
			
			try{
				BangoAgent.debug("sending " + profile.getProfileId());
				
				for(var ax:String in profile.Events){
					BangoAgent.debug(profile.Events[ax].EventName)
				}
				
				var loader : URLLoader = new URLLoader();  
				var request : URLRequest = new URLRequest(BangoConstants.SETTING_POST_URL);  
				request.method = URLRequestMethod.POST;  
				var variables : URLVariables = new URLVariables();  
				request.data = JSON.stringify(profile);
				
				request.contentType="application/json";
				request.userAgent = _userAgent;

				loader.addEventListener(Event.COMPLETE, function(e:Event):void {
					postCallback(e,profile);
				});
				
				loader.load(request); 
				
				if(_useOfflineStorage == true){
					BangoDatabaseStorageHelper.UpdateProfileLock(profile.getProfileId(),true);
				}
			}catch(err:Error){
				
				returnValue = false;
				BangoAgent.debug("Error" + err.message.toString());
				BangoAgent.debug(err.getStackTrace());
				
			}
			
			return returnValue;
		}
		
		/**
		 * Callback for the sendEvents call. If the post was succefull, uodate the database
		 * 
		 * @param event
		 *            The profile to post to the specified url.
		 * @param profileId
		 *            The profile ID associated to the call.
		 * 
		 */
		public function postCallback(event:Event,profile:BangoProfile):void{
			if(_useOfflineStorage == true){
				BangoDatabaseStorageHelper.UpdateProfileLock(profile.getProfileId(),false);
			}
			var result:String = URLLoader(event.target).data.toString();
			BangoAgent.debug("GOT " + result);
			if(_useOfflineStorage){
				try{
					if(result.indexOf("OK") == 0){
						BangoDatabaseStorageHelper.UpdateEventsPosted(profile.getProfileId());
						if(profile.getProfileId() != BangoDatabaseStorageHelper.ProfileId()){
							BangoAgent.debug("Delete" + profile.getProfileId());
							BangoDatabaseStorageHelper.DeleteEventsPosted(profile.getProfileId());
						}
					}else{
						BangoDatabaseStorageHelper.DeleteEventsPosted(profile.getProfileId());
					}
				}catch(err:Error){
					BangoAgent.debug("Error " + err.message.toString() + err.getStackTrace());
				}
			}else{
				if(result == "OK"){
					_profile.Events= new Array();
				}
			}
			
		}
		
		/**
		 * Logs an the session start event to the internal logging engine.
		 * @param eventValue
		 *            Option. The value of the event to log.
		 * @param eventDetail
		 *            Option. The detail of the event to log.
		 * @param eventParams
		 *            Option. Any custom parameters to log with events.
		 * 
		 * @return The internal id of the event.
		 */
		public function logEventStart(eventValue:String = "",eventDetail:String = "",eventParams:Dictionary = null):Number{
			return logEvent(BangoSessionEvents.SESSION_START,eventValue,eventDetail,eventParams);
		}
		
		/**
	 	 * Logs an the session end event to the internal logging engine.
		 * @param eventValue
		 *            Option. The value of the event to log.
		 * @param eventDetail
		 *            Option. The detail of the event to log.
		 * @param eventParams
		 *            Option. Any custom parameters to log with events.
		 * 
		 * @return The internal id of the event.
		 */
		public function logEventEnd(eventValue:String = "",eventDetail:String = "",eventParams:Dictionary = null):Number{
			return logEvent(BangoSessionEvents.SESSION_END,eventValue,eventDetail,eventParams);
		}
		
		/**
		 * Logs an error event to the internal logging engine.
		 * 
		 * @param errorId
		 *            The error Id of the event to log.
		 * @param errorClass
		 *            The class in which the error occurred.
		 * @param message
		 *            The error message.
		 * @param eventParameters
		 *            Any custom parameters to log with events.
		 * @return The internal id of the error.
		 */
		public function logEventError(errorId:String = "",errorClass:String = "",message:String = "",eventParams:Dictionary = null):Number{
			return logEvent(BangoSessionEvents.EVENT_ERROR,errorClass,message,eventParams,BangoConstants.ERROR_USER,errorId);
		}
		
		/**
		 * Logs a critical internal error event to the internal logging engine.
		 * 
		 * @param ex
		 *            The error that was raised.
		 * @return The internal id of the error.
		 */
		public function LogInternalErrorCritical(ex:Error):Number{
			return logInternalError(ex,BangoConstants.ERROR_INTERNAL_CRITICAL);
		}
		
		/**
		 * Logs a warning internal error event to the internal logging engine.
		 * 
		 * @param ex
		 *            The error that was raised.
		 * @return The internal id of the error.
		 */
		public function LogInternalErrorWarning(ex:Error):Number{
			return logInternalError(ex,BangoConstants.ERROR_INTERNAL_WARNING);
		}
		
		/**
		 * Logs an internal error event to the internal logging engine.
		 * 
		 * @param ex
		 *            The error that was raised.
		 * @param errorLevel
		 *            The error level of the error (Warning/Critical)
		 * @return The internal id of the error.
		 */
		public function logInternalError(ex:Error, level:Number):Number{
			return logEvent("",ex.message,ex.getStackTrace(),null,level,"");
		}
		
		/**
		 * Logs an event to the internal logging engine.
		 * 
		 * @param eventName
		 *            The name of the event to log.
		 * @param eventValue
		 *            The value of the event to log.
		 * @param eventDetail
		 *            The detail of the event to log.
		 * @param eventParameters
		 *            Any custom parameters to log with events.
		 * @param errorLevel
		 *            The error level of the event to log.
		 * @param errorId
		 *            The error Id of the event to log.
		 * @return the internal id of the event.
		 */
		public function logEvent(eventName:String, eventValue:String = "", eventDetail:String= "", eventParameters:Dictionary = null,errorLevel:int = BangoConstants.ERROR_NONE,errorId:String = ""):int{
			var returnValue:int = -1;
			
			var eventLocalTime:Date = new Date();
			var difference:Number = eventLocalTime.timezoneOffset * 60000;
			var eventTime:Date = new Date(eventLocalTime.time + difference);
			
			var formatter:DateFormatter = new DateFormatter();
			formatter.formatString = "YYYY-MM-DDTh:NN:SS.QQQ";
			
			var eventTimeStr:String = formatter.format(eventTime);
			eventTimeStr = eventTimeStr.replace("h",padHours(eventTime.hours.toString()));
			var eventLocalTimeStr:String = formatter.format(eventLocalTime);
			eventLocalTimeStr = eventLocalTimeStr.replace("h",padHours(eventLocalTime.hours.toString()));
			
			var eventParams:Array = new Array();
			if(_logEnabled){
				if(eventParameters != null){
					for (var k:Object in eventParameters) {
						var tmp:Object = new Object();
						tmp[BangoFieldNames.POST_PARAMETERNAME] =k;
						tmp[BangoFieldNames.POST_PARAMETERVALUE]=eventParameters[k];
						eventParams.push(tmp);
					}
				}
				var eventId:Number = (_useOfflineStorage)?-1:_profile.Events.length1;
				var event:BangoEvent = new BangoEvent(eventId,
					BangoStatic.ConnectipnType(_interface.getProperty(BangoFieldNames.POST_CONNECTIONTYPE)), 
					BangoStatic.OperatorConnectipnType(_interface.getProperty(BangoFieldNames.POST_OPERATORCONNECTIONTYPE)), 
					errorLevel, 
					eventName,
					eventDetail, 
					eventValue, 
					errorId, 
					eventTimeStr, 
					eventLocalTimeStr,
					eventParams
				);
				
				_profile.pushEvents(event);
				
				if(_useOfflineStorage){
					returnValue = BangoDatabaseStorageHelper.LogEvent(event);
					if(_profile.Events.length >= maximumEventsPerProfile){
						BangoDatabaseStorageHelper.LogProfile(_profile);
						_profile.Events = new Array();
					}
				}else{
					returnValue = _profile.Events.length;
				}
			}
			
			return returnValue;
		}
		
		//Actionscript format the hours in the date 0-23. Bango needs a 2 digit format always. 
		//We must make sure to add a 0 when needed
		private function padHours(hours:String):String{
			if(hours.length < 2)
				return "0" + hours;
			return hours;
		}
		
		/**
		 * Gets the application version from the application configuration, in the Descriptor versionNumber.
		 * 
		 * @return The application version.
		 */
		private function getVersion():String{
			var descriptor:XML =NativeApplication.nativeApplication.applicationDescriptor;
			var ns:Namespace = descriptor.namespaceDeclarations()[0];
			return descriptor.ns::versionNumber;
		}
		
		public function viewBatch():String{
			var bangoProfiles:Array = new Array();
			if(_useOfflineStorage){
				bangoProfiles = BangoDatabaseStorageHelper.GetProfilesForPost(false);
				for(var obj:Object in bangoProfiles){
					var profile:BangoProfile = bangoProfiles[obj] as BangoProfile;
					profile.Events = BangoDatabaseStorageHelper.GetEventsForPost(profile.getProfileId());
				}
			}else{
				bangoProfiles.push(_profile);
			}
			return JSON.stringify(bangoProfiles);
		}
		
		/**
		 * Initialises the location provider.
		 * 
		 */
		private function getLocation():void{
			if (_findLocation){
				if (Geolocation.isSupported) {
					_locationManager = new Geolocation();
					_locationManager.setRequestedUpdateInterval(1000);
					_locationManager.addEventListener(GeolocationEvent.UPDATE, geolocationUpdateHandler);
				}
			}
		}

		/**
		 * Updates the profile with the new location.
		 * 
		 * @param location
		 *            The new location of the user.
		 */
		private function geolocationUpdateHandler(event:GeolocationEvent):void{
			
			if (_useOfflineStorage){
				BangoDatabaseStorageHelper.UpdateProfileLocation(_locationSource, event.latitude, event.longitude, event.altitude, event.horizontalAccuracy);
			}else{
				_profile.LocationSource =_locationSource;
				_profile.LocationY =event.latitude;
				_profile.LocationX = event.longitude;
				_profile.LocationAccuracy = event.horizontalAccuracy;
				_profile.LocationZ = event.altitude;
				_locationManager.setRequestedUpdateInterval(600000);
			}
			
		}
		
		
		/**
		 * Destroys the location finder.
		 */
		public function EndLocationUpdates():void{
			if (_findLocation){
				if (Geolocation.isSupported) {
					_locationManager.removeEventListener(GeolocationEvent.UPDATE, geolocationUpdateHandler);
				}
			}
		}
		
	}
}